Una red neuronal artificial (RNA) es un sistema computacional inspirado en el funcionamiento del cerebro humano. Está compuesto por unidades básicas llamadas neuronas artificiales, que se interconectan entre sí formando una red. Estas neuronas artificiales simulan el comportamiento de las neuronas biológicas, recibiendo información, procesándola y transmitiéndola a otras neuronas.
Las redes neuronales artificiales se pueden entrenar para realizar una amplia variedad de tareas, como:
Las redes neuronales artificiales se han convertido en una herramienta fundamental en el campo de la inteligencia artificial, con aplicaciones en diversos sectores como la medicina, las finanzas, la industria y el marketing.
Características de las redes neuronales artificiales:
Ejemplos de cómo se utilizan las redes neuronales artificiales en la actualidad:
Las redes neuronales artificiales (RNA) se pueden usar en la analítica empresarial para una amplia variedad de tareas, incluyendo:
1. Segmentación de clientes: Las RNA pueden usarse para identificar grupos de clientes con características similares, lo que permite a las empresas personalizar sus ofertas y estrategias de marketing.
2. Predicción de la demanda: Las RNA pueden usarse para pronosticar la demanda de productos o servicios, lo que ayuda a las empresas a optimizar sus inventarios y recursos.
3. Detección de fraude: Las RNA pueden usarse para detectar transacciones fraudulentas, lo que ayuda a las empresas a protegerse de pérdidas financieras.
4. Análisis de riesgos: Las RNA pueden usarse para evaluar los riesgos asociados con diferentes decisiones comerciales, lo que ayuda a las empresas a tomar decisiones más informadas.
5. Optimización de precios: Las RNA pueden usarse para determinar el precio óptimo para productos o servicios, lo que ayuda a las empresas a maximizar sus ganancias.
6. Análisis del sentimiento del cliente: Las RNA pueden usarse para analizar las opiniones de los clientes en las redes sociales y otras plataformas, lo que ayuda a las empresas a comprender mejor las necesidades y expectativas de sus clientes.
7. Automatización de tareas: Las RNA pueden usarse para automatizar tareas repetitivas, como la clasificación de documentos o la extracción de datos, lo que libera tiempo para que los empleados se concentren en tareas más estratégicas.
8. Detección de anomalías: Las RNA pueden usarse para detectar anomalías en los datos, como picos repentinos en las ventas o cambios en el comportamiento del cliente, lo que puede ayudar a las empresas a identificar problemas potenciales y tomar medidas correctivas.
9. Personalización de recomendaciones: Las RNA pueden usarse para recomendar productos o servicios a los clientes en función de sus intereses y preferencias, lo que aumenta la satisfacción del cliente y las ventas.
10. Mejora de la atención al cliente: Las RNA pueden usarse para crear chatbots que puedan responder a las preguntas de los clientes y brindar asistencia, lo que mejora la experiencia del cliente y reduce los costos de atención al cliente.
En resumen, las RNA son una herramienta poderosa que puede usarse para mejorar la analítica empresarial en una variedad de maneras. Al aprovechar el poder de las RNA, las empresas pueden obtener información valiosa de sus datos, tomar decisiones más informadas y mejorar sus resultados.
Uso de las RNA en la analítica empresarial:
Las RNA son una tecnología en constante evolución con un gran potencial para la analítica empresarial. A medida que las RNA continúen desarrollándose, podemos esperar ver aún más aplicaciones innovadoras en el futuro.
Una neurona artificial, también conocida como neurona formal o perceptrón, es un modelo matemático simple que simula el comportamiento de una neurona biológica. Es la unidad fundamental de las redes neuronales artificiales (RNA).

Funcionamiento:
Ejemplo:
Supongamos que tenemos una neurona artificial con tres entradas $ x_1, x_2, x_3 $ y sus respectivos pesos $ w_1, w_2, w_3 $. La neurona también tiene un sesgo (bias) $ b = w_b \times 1$. La función de activación que utilizaremos es la función sigmoide, que tiene la siguiente fórmula:
$$ f(z) = \frac{1}{1 + e^{-z}} $$Donde $ z $ es la suma ponderada de las entradas y los pesos más el sesgo:
$$ z = x_1 \times w_1 + x_2 \times w_2 + x_3 \times w_3 + b $$Entradas y Pesos: Supongamos que tenemos:
Suma ponderada: Calculamos $ z $: $$ z = 0.5 \times 0.2 + 0.3 \times 0.4 + 0.8 \times 0.7 - 0.3 = 0.1 + 0.12 + 0.56 - 0.3 = 0.48 $$
Función de activación: Ahora, aplicamos la función sigmoide a $ z $: $$ f(z) = \frac{1}{1 + e^{-0.48}} \approx \frac{1}{1 + e^{-0.48}} \approx \frac{1}{1 + 0.618} \approx \frac{1}{1.618} \approx 0.618 $$
La salida de esta neurona artificial, utilizando la función de activación sigmoide, sería aproximadamente $ 0.618 $.
Esta salida se puede utilizar como entrada para otras neuronas en una red neuronal más grande o como salida final, dependiendo de la arquitectura y la tarea específica que esté realizando la red neuronal.
Las funciones de activación son elementos fundamentales en las redes neuronales artificiales, ya que introducen no linealidades en el modelo, permitiendo que la red pueda aprender y modelar relaciones complejas en los datos. A continuación tienes una descripción de algunas de las funciones de activación más importantes:
Función Sigmoide:
Función ReLU (Rectified Linear Unit):
Función Tangente Hiperbólica (Tanh):
Función Lineal:
Función Softmax:
Estas son algunas de las funciones de activación más importantes y utilizadas en redes neuronales artificiales. La elección de la función de activación adecuada depende de la naturaleza de la tarea y la arquitectura de la red neuronal.
Una red neuronal feedforward, también conocida como perceptrón multicapa o red neuronal de propagación hacia adelante, es uno de los tipos más simples y comunes de redes neuronales artificiales.
Arquitectura: Una red neuronal feedforward está compuesta por una serie de capas de neuronas, que están dispuestas en forma de grafo acíclico dirigido. Las neuronas en una capa están conectadas con todas las neuronas de la capa siguiente, pero no hay conexiones hacia atrás, lo que significa que la información fluye en una sola dirección, de entrada a salida.
Capas: La red consta de tres tipos de capas:
Propagación hacia adelante (Forward Propagation): El proceso de propagación hacia adelante comienza con la introducción de los datos en la capa de entrada. Cada neurona en cada capa oculta y de salida calcula una combinación lineal de las entradas ponderadas por los pesos de las conexiones y luego aplica una función de activación a esta combinación lineal para producir la salida de la neurona.
Funciones de activación: Como mencionado anteriormente, cada neurona en la red utiliza una función de activación para introducir no linealidades en el modelo. Las funciones de activación más comunes son la función sigmoide, ReLU (Rectified Linear Unit), tanh (tangente hiperbólica) y softmax (en la capa de salida para tareas de clasificación multiclase).
Entrenamiento: El entrenamiento de la red neuronal feedforward se realiza utilizando algoritmos de optimización como el descenso del gradiente estocástico (SGD) o sus variantes, donde se ajustan los pesos de las conexiones para minimizar una función de pérdida que mide la discrepancia entre las salidas predichas por la red y las salidas reales.
Retropropagación (Backpropagation): Una vez calculada la pérdida, se propaga hacia atrás a través de la red utilizando el algoritmo de retropropagación, ajustando los pesos de las conexiones en función del gradiente de la función de pérdida con respecto a los pesos. Este proceso se repite iterativamente durante múltiples épocas hasta que la red converge y la pérdida se minimiza.
Una red neuronal feedforward funciona propagando la información hacia adelante a través de múltiples capas de neuronas, calculando las salidas en cada capa mediante combinaciones lineales de las entradas ponderadas por los pesos y aplicando funciones de activación no lineales. Luego, se ajustan los pesos durante el entrenamiento para minimizar la pérdida utilizando algoritmos de optimización como el descenso del gradiente estocástico y la retropropagación.

La propagación feedforward de manera matricial para una capa oculta en un MLP es la siguiente:
$$ Z^l = W^l \cdot A^{l-1} + B^l $$$$ A^l = \sigma(Z^l) $$Donde:
Para la capa de salida, la fórmula es similar pero sin aplicar la función de activación si la salida es lineal, o aplicando la función de activación adecuada si la salida es no lineal.
Esta formulación matricial permite realizar eficientemente el cálculo del feedforward utilizando operaciones matriciales y de vectores, lo que es especialmente útil en implementaciones computacionales eficientes de MLPs.
La retropropagación (backpropagation) es el algoritmo fundamental utilizado para entrenar redes neuronales mediante el cálculo eficiente de los gradientes de la función de pérdida con respecto a los pesos de la red. Este algoritmo se basa en el principio del descenso del gradiente, donde los pesos se actualizan en la dirección opuesta al gradiente de la función de pérdida con respecto a los pesos, con el objetivo de minimizar la pérdida.
A continuación se detalla el proceso de retropropagación paso a paso, incluyendo la matemática involucrada:
Supongamos que tenemos una red neuronal feedforward con múltiples capas, y queremos entrenarla para una tarea de clasificación. Sea $ L $ la función de pérdida que queremos minimizar, que generalmente se define como la diferencia entre las salidas predichas ($ \hat{y} $) y las salidas reales ($ y $).
Propagación hacia adelante (Forward Propagation):
Cálculo de la pérdida (Loss Calculation):
Retropropagación (Backpropagation):
Actualización de pesos (Weight Update):
Este proceso de propagación hacia adelante, cálculo de la pérdida, retropropagación y actualización de pesos se repite iterativamente durante múltiples épocas de entrenamiento hasta que la función de pérdida converge y los pesos de la red se ajustan adecuadamente para la tarea dada.
La retropropagación es el algoritmo clave para entrenar redes neuronales mediante el cálculo eficiente de los gradientes de la función de pérdida con respecto a los pesos de la red, lo que permite ajustar los pesos para minimizar la pérdida y mejorar el rendimiento de la red en la tarea específica.
El gradiente es un concepto fundamental en el cálculo vectorial que se utiliza ampliamente en diversos campos, incluyendo matemáticas, física, ingeniería y aprendizaje automático. En el contexto del aprendizaje automático y la optimización de funciones, el gradiente se refiere a la dirección y la tasa de cambio más pronunciada de una función multivariable en un punto dado.
Formalmente, el gradiente de una función escalar $ f $ de $ n $ variables, denotada como $ \nabla f $ o $ \frac{\partial f}{\partial \mathbf{x}} $, es un vector que apunta en la dirección donde la función crece más rápidamente, y su magnitud indica la tasa de cambio en esa dirección. Matemáticamente, el gradiente se define como un vector compuesto de las derivadas parciales de la función con respecto a cada una de las variables independientes.
Para una función $ f(\mathbf{x}) $ donde $ \mathbf{x} = (x_1, x_2, ..., x_n) $ es un vector de variables independientes, el gradiente se calcula como:
$$ \nabla f = \left( \frac{\partial f}{\partial x_1}, \frac{\partial f}{\partial x_2}, ..., \frac{\partial f}{\partial x_n} \right) $$En términos más simples, el gradiente indica la dirección en la cual la función aumenta más rápidamente. Por lo tanto, en el contexto de la optimización de funciones, el gradiente se utiliza para encontrar los mínimos o máximos locales de una función. En el aprendizaje automático, especialmente en algoritmos de optimización como el descenso del gradiente, el gradiente de la función de pérdida con respecto a los parámetros del modelo se utiliza para ajustar los parámetros de manera iterativa, buscando minimizar la función de pérdida y mejorar el rendimiento del modelo.
Una red neuronal feedforward se puede utilizar para problemas de clasificación mediante un proceso de entrenamiento donde la red aprende a asignar entradas a diferentes categorías o clases.
A continuación se detalla cómo funciona una red feedforward para un problema de clasificación:
Preparación de datos: Primero, necesitas preparar tus datos de entrenamiento. Esto incluye dividir tus datos en conjuntos de entrenamiento, validación y prueba, así como realizar cualquier preprocesamiento necesario, como normalización de características.
Diseño de la red neuronal:

Inicialización de pesos: Inicializa los pesos de la red de manera aleatoria o utilizando algún método de inicialización de pesos como Xavier o He.
Propagación hacia adelante (Forward Propagation):
Cálculo de la pérdida: Calcula la función de pérdida, que mide la discrepancia entre las salidas predichas por la red y las clases reales de tus datos de entrenamiento. Para problemas de clasificación, una función de pérdida común es la entropía cruzada categórica (categorical cross-entropy).
Retropropagación (Backpropagation):
Validación y ajuste de hiperparámetros:
Prueba del modelo:
Este proceso se repite iterativamente durante múltiples épocas de entrenamiento hasta que la función de pérdida converja y la red neuronal aprenda a realizar clasificaciones precisas en el conjunto de entrenamiento y generalice bien a datos no vistos.
Para un problema de regresión, donde el objetivo es predecir un valor numérico continuo en lugar de una categoría, puedes utilizar una red neuronal feedforward de manera similar, pero con algunas diferencias en la arquitectura de la red y la función de pérdida.
Funcionamiento de una red feedforward para un problema de regresión:
Preparación de datos: Al igual que en el caso de clasificación, necesitas preparar tus datos de entrenamiento, dividirlos en conjuntos de entrenamiento, validación y prueba, y realizar cualquier preprocesamiento necesario, como normalización de características.
Diseño de la red neuronal:

Inicialización de pesos: Inicializa los pesos de la red de manera aleatoria o utilizando algún método de inicialización de pesos.
Propagación hacia adelante (Forward Propagation):
Cálculo de la pérdida: Calcula la función de pérdida, que mide la discrepancia entre las salidas predichas por la red y los valores reales de tus datos de entrenamiento. Para problemas de regresión, una función de pérdida común es el error cuadrático medio (Mean Squared Error, MSE).
Retropropagación (Backpropagation):
Validación y ajuste de hiperparámetros:
Prueba del modelo:
Este proceso se repite iterativamente durante múltiples épocas de entrenamiento hasta que la función de pérdida converja y la red neuronal aprenda a realizar predicciones precisas de valores numéricos continuos en el conjunto de entrenamiento y generalice bien a datos no vistos.
El problema XOR es un desafío clásico en el ámbito del aprendizaje automático y las redes neuronales. XOR es una operación lógica que toma dos entradas binarias y devuelve verdadero (1) si solo una de las entradas es verdadera (1), y falso (0) en cualquier otro caso. La tabla de verdad de la operación XOR es la siguiente:
| Entrada X1 | Entrada X2 | Salida XOR, y | ||
|---|---|---|---|---|
| 0 | 0 | 0 | ||
| 0 | 1 | 1 | ||
| 1 | 0 | 1 | ||
| 1 | 1 | 0 |
A simple vista, parece un problema sencillo de resolver manualmente. Sin embargo, el desafío surge cuando intentamos utilizar un único perceptrón (una sola neurona) para aprender y predecir la salida de la operación XOR. Esto se debe a que la relación entre las entradas y las salidas no es linealmente separable.
Para resolver el problema XOR utilizando un único perceptrón, necesitaríamos una función de activación no lineal, como la función sigmoide. Sin embargo, incluso con la función sigmoide, un único perceptrón no puede modelar correctamente la operación XOR.
La solución al problema XOR implica el uso de una arquitectura de red neuronal más compleja, como un Perceptrón Multicapa (MLP). Con un MLP, que consta de al menos una capa oculta, podemos capturar y aprender las relaciones no lineales entre las entradas y las salidas, lo que nos permite resolver el problema XOR con alta precisión.


# import regressor
from sklearn.neural_network import MLPRegressor
# import classifier
from sklearn.neural_network import MLPClassifier
from sklearn.model_selection import train_test_split
from sklearn.metrics import confusion_matrix, classification_report
# import the KNNimputer class
from sklearn.impute import KNNImputer
# import the StandardScaler
from sklearn.preprocessing import StandardScaler
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.metrics import mean_squared_error
import ipywidgets as widgets
import ipywidgets
import warnings
warnings.filterwarnings('ignore')
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
from matplotlib import cm
from matplotlib.ticker import LinearLocator, FormatStrFormatter
# Import linear regression model
from sklearn.linear_model import LinearRegression
# Min max scaler
from sklearn.preprocessing import MinMaxScaler
def mlp(x_input, y_output, hidden_neurons, training_steps=5000, lr=0.7, batch_size=1):
# Input size (N_i: input neurons), and output classes (N_o: output neurongs, size of the output)
N_i, N_o = x_input.shape[0], y_output.shape[0]
# Initialize randomly the weights
# Hidden layer
w_h = np.random.rand(hidden_neurons+1,N_i) - 0.5
# Output layer
w_o=np.random.rand(N_o,hidden_neurons+1) - 0.5
mse = []
for ti in range(training_steps):
# Select training pattern randomly
#i = np.floor(4*np.random.rand()).astype('int')
for bi in range(batch_size):
# Feed-forward the input to hidden layer
i = np.floor(np.shape(x_input)[1]*np.random.rand()).astype('int')
r_h = 1 / (1 + np.exp(-w_h*x_input[:,i]))
r_h[-1] = 1 # Bias from hidden to output layer
#r_h = np.concatenate((r_h, np.ones((1,1))), axis=0)
# Feed-forward the input to the output layer
r_o = 1 / (1 + np.exp(-w_o*r_h))
# Calculate the network error
d_o = np.multiply(np.multiply(r_o, 1-r_o), y_output[:,i] - r_o)
# Calculate the responsibility of the hidden network in the error
d_h = np.multiply(np.multiply(r_h, (1-r_h)), (w_o.T*d_o))
# Update weights
w_o = w_o + lr*(r_h*d_o.T).T
w_h = w_h + lr*(x_input[:,i]*d_h.T).T
# Test all patterns
rht = 1 / (1 + np.exp(-w_h*x_input))
rht[-1] = 1
r_o_test = 1 / (1 + np.exp(-w_o*rht))
mse += [mean_squared_error(np.array(y_output), np.array(r_o_test))]
return mse, r_o_test, w_h, w_o
def mlp_predict(x_input, w_h, w_o):
# Test all patterns
#r_o_test = 1 / (1 + np.exp(-w_o*(1/(1+np.exp(-w_h*r_i)))))
rht = 1 / (1 + np.exp(-w_h*x_input))
rht[-1] = 1 # bias
r_o_test = 1 / (1 + np.exp(-w_o*rht))
return r_o_test
# XOR input: last neuron always one is the bias: 1 1 1 1
x_input = np.matrix('0 1 0 1; 0 0 1 1; 1 1 1 1')
# XOR output
# Two classes encoding
#y_output = np.matrix('0 1 0 1; 0 0 1 1; 1 1 1 1')
# XOR Output
y_output = np.matrix('0 1 1 0') # internally is a regression problem
#plt.figure(figsize=(12,6))
@ipywidgets.interact
def plot(hidden = [2, 4, 10, 20],
epochs = [5000, 1000, 10000],
learning_rate = [0.7, 0.1, 0.3, 0.5, 0.9],
plot3d=False
):
fig = plt.figure(constrained_layout=True, figsize=(12,6))
gs = fig.add_gridspec(1, 3)
ax1 = fig.add_subplot(gs[0])
ax2 = fig.add_subplot(gs[1:])
result = mlp(x_input, y_output, hidden_neurons=hidden,
training_steps=epochs, lr=learning_rate, batch_size=1)
ax1.plot(result[0])
ax1.set_xlabel("epochs")
ax1.set_ylabel("Loss (MSE)")
x_min, x_max = -0.1, 1.1
y_min, y_max = -0.1, 1.1
gX = np.linspace(x_min, x_max, 100)
gY = np.linspace(y_min, y_max, 100)
gData = np.concatenate((gX.reshape(-1,1), gY.reshape(-1,1)), axis=1)
# Input, ones is the biased
gData = np.concatenate((gData, np.ones((100,1))), axis=1)
x_input_s = np.matrix(gData.transpose())
y_test = mlp_predict(x_input_s, result[2], result[3])
xx, yy = np.meshgrid(np.linspace(x_min, x_max, 100),
np.linspace(y_min, y_max, 100))
Xgd = gData[:,0]
Ygd = gData[:,1]
Z = []
for xi in Xgd:
for yi in Ygd:
z_result = mlp_predict(np.array([[xi, yi, 1]]).T, result[2], result[3])
#Z += [np.where(z_result == z_result.max())[0][0]]
Z += [np.array(z_result[0]).flatten()[0]]
Z = np.array(Z)
Z = Z.reshape(xx.shape)
# Plot contour
cs = ax2.contourf(xx, yy, Z, cmap=plt.cm.RdYlBu)
csl = ax2.contour(xx, yy, Z, cmap=plt.cm.RdYlBu)
# Make a colorbar for the cs returned by the contourf call.
cbar = plt.colorbar(cs, ax=ax2)
cbar.ax.set_ylabel('Network output')
# Add the contour line levels to the colorbar
cbar.add_lines(csl)
# Plot the training points
data = pd.DataFrame(x_input[:-1].T, columns=["X1", "X2"])
data["y"] =y_output.T
data.y = pd.Categorical(data.y)
sns.scatterplot(data=data, x="X1", y="X2", hue="y", palette=["b", "g"], ax=ax2, s=100)
plt.suptitle("Decision surface of a mlp classifier")
plt.legend(loc='center right', borderpad=0, handletextpad=0)
plt.axis("tight")
plt.show()
if plot3d:
fig = plt.figure()
ax = fig.add_subplot(projection = '3d')
surf = ax.plot_surface(xx, yy, Z, cmap=plt.cm.RdYlBu,
linewidth=0, antialiased=False)
ax.view_init(30, -60)
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Network response')
plt.show()
interactive(children=(Dropdown(description='hidden', options=(2, 4, 10, 20), value=2), Dropdown(description='e…
Una superficie de decisión, también conocida como frontera de decisión o límite de decisión, es una representación visual en un espacio de características de cómo un modelo de aprendizaje automático divide o clasifica diferentes clases o categorías de datos. Esta superficie o límite separa las regiones del espacio de características en las que el modelo predice una clase u otra.
En problemas de clasificación binaria, la superficie de decisión divide el espacio de características en dos regiones: una región donde se predice una clase y otra donde se predice la otra clase. Para problemas de clasificación con múltiples clases, habrá múltiples límites de decisión que separan las diferentes clases.
La forma y la complejidad de la superficie de decisión pueden variar según el modelo utilizado y la complejidad del problema. Por ejemplo:
Visualizar la superficie de decisión puede ser útil para comprender cómo un modelo está haciendo sus predicciones y para identificar posibles problemas como el sobreajuste. Sin embargo, en problemas de alta dimensionalidad, la visualización directa de la superficie de decisión puede no ser práctica, y pueden ser necesarias técnicas de reducción de dimensionalidad o visualización alternativas.
Vamos a construir una regresión lineal simple y contrastar el modelo contra un perceptrón multicapa.
Variables:
En particular en este ejercicio queremos demostrar el sobreajuste de un modelo MLP.
El sobreajuste, o overfitting en inglés, es un fenómeno común en el aprendizaje automático que ocurre cuando un modelo se ajusta demasiado bien a los datos de entrenamiento, hasta el punto de capturar el ruido o las fluctuaciones aleatorias en los datos en lugar de aprender las relaciones subyacentes. Como resultado, el modelo puede tener un rendimiento deficiente cuando se enfrenta a datos nuevos o no vistos, lo que significa que no generaliza bien.
El sobreajuste puede ocurrir cuando el modelo es demasiado complejo en relación con la cantidad de datos disponibles para el entrenamiento. Algunos signos comunes de sobreajuste incluyen:
Bajo rendimiento en datos de prueba: A pesar de tener un rendimiento excelente en los datos de entrenamiento, el modelo muestra un rendimiento deficiente en datos de prueba o validación que no ha visto durante el entrenamiento.
Alta varianza: El modelo es altamente sensible a pequeñas variaciones en los datos de entrenamiento, lo que resulta en diferentes resultados cuando se entrena con diferentes subconjuntos de datos.
Coeficientes excesivamente grandes: En modelos lineales, los coeficientes de las características pueden volverse muy grandes para ajustarse a los datos de entrenamiento, lo que indica que el modelo está capturando el ruido en lugar de la señal.
Curva de aprendizaje divergente: Las curvas de aprendizaje del modelo pueden mostrar una divergencia entre el error de entrenamiento y el error de validación, donde el error de entrenamiento continúa disminuyendo mientras que el error de validación comienza a aumentar.
Para evitar el sobreajuste, es importante utilizar técnicas como la regularización, la validación cruzada, el conjunto de validación y el ajuste de hiperparámetros para controlar la complejidad del modelo y garantizar que generalice bien a datos no vistos. Estas técnicas ayudan a encontrar el equilibrio adecuado entre el sesgo y la varianza del modelo, lo que conduce a un mejor rendimiento general en datos nuevos.
Podemos observar (en el panel izquierdo) que para 10 neuronas en la capa oculta, obtenemos un modelo de MLP (rojo), cercano a la curva de regresión lineal (verde).
Para 50 neuronas en la capa oculta (en el panel central), el MLP replica la línea de regresión lineal, es decir ha sido capaz de construir un modelo tan bueno como el mejor.
Para 100 neuronas (en el panel derecho), el MLP tiene un error MSE, más pequeño que la regresión lineal, sin embargo es el peor de los modelos MLP conseguidos.

# Datos
x = np.array([63, 64, 66, 69, 69, 71, 71, 72, 73, 75]) # altura (pulgadas)
y = np.array([127, 121, 142, 157, 162, 156, 169, 165, 181, 208]) # peso (libras)
@ipywidgets.interact
def plot(N_h = [5, 10, 50, 100, 500, 1000],
zoom=False,
):
mlpreg = MLPRegressor(activation='relu',max_iter=10000,hidden_layer_sizes=(N_h,))
x_scaled = (x - x.min()) / (x.max() - x.min()) # min-max normalization
mlpreg.fit(x_scaled.reshape(-1,1),y)
x_test = np.linspace(50, 100)
x_test_scaled = (x_test - x.min()) / (x.max() - x.min())
y_mlp = mlpreg.predict(x_test_scaled.reshape(-1, 1))
plt.scatter(x, y, label='Data')
plt.plot(x_test, y_mlp, '--r', label='MLP')
# Instance model
lin_reg = LinearRegression()
# Fit model (train)
lin_reg.fit(x.reshape(-1,1), y) # our lineal model (base model)
y_lr_test = lin_reg.coef_*x_test + lin_reg.intercept_ # linear model
plt.plot(x_test, y_lr_test, ':g', label='LR')
l=plt.legend()
if zoom:
plt.xlim(x.min(), x.max())
interactive(children=(Dropdown(description='N_h', options=(5, 10, 50, 100, 500, 1000), value=5), Checkbox(valu…
MNIST (Modified National Institute of Standards and Technology database) es un conjunto de datos ampliamente utilizado en el campo del aprendizaje automático y la visión por computadora. Contiene un conjunto de imágenes en escala de grises de dígitos escritos a mano, cada uno en un formato de 28x28=784 píxeles. Estas imágenes están etiquetadas con los dígitos correspondientes del 0 al 9.
El conjunto de datos MNIST es utilizado principalmente como un punto de referencia para probar y comparar algoritmos y modelos de clasificación de imágenes. Es un conjunto de datos estándar en la comunidad de aprendizaje automático y se ha utilizado en una amplia variedad de aplicaciones y experimentos.
Debido a su simplicidad y tamaño manejable, MNIST se ha convertido en un punto de partida común para aquellos que desean aprender sobre el aprendizaje automático y comenzar a trabajar con modelos de redes neuronales.
El MNIST digits es esencialmente un subconjunto de MNIST, centrado específicamente en los dígitos escritos a mano. Contiene 60,000 imágenes de entrenamiento y 10,000 imágenes de prueba, cada una etiquetada con el dígito que representa.
El MNIST digits es un problema de clasificación de imágenes. El objetivo es clasificar imágenes en escala de grises de dígitos escritos a mano en sus respectivas categorías numéricas, que van del 0 al 9. Cada imagen en el conjunto de datos MNIST es un cuadrado de 28x28 píxeles, lo que resulta en un total de 784 píxeles por imagen. Cada píxel tiene un valor de intensidad de 0 a 255, donde 0 representa blanco y 255 representa negro.
El desafío consiste en entrenar un modelo de aprendizaje automático o una red neuronal que pueda tomar una imagen de un dígito escrito a mano como entrada y predecir correctamente el dígito que representa. Esencialmente, el modelo debe aprender a reconocer y diferenciar entre diferentes formas de dígitos escritos a mano, independientemente de su estilo de escritura o variaciones en la orientación y el tamaño. Es un problema de clasificación, aunque puede tratarse como uno de regresión.
Este problema es un problema clásico en el campo del aprendizaje automático y la visión por computadora, y se utiliza comúnmente como un punto de referencia para probar y comparar algoritmos y modelos de clasificación de imágenes. La precisión de los modelos en el conjunto de datos MNIST es a menudo considerada como un indicador de su capacidad para generalizar y manejar problemas de clasificación de imágenes más complejos en la vida real.
mnist_digits = pd.read_csv('https://raw.githubusercontent.com/sbussmann/kaggle-mnist/master/Data/train.csv')
mnist_digits.head()
| label | pixel0 | pixel1 | pixel2 | pixel3 | pixel4 | pixel5 | pixel6 | pixel7 | pixel8 | ... | pixel774 | pixel775 | pixel776 | pixel777 | pixel778 | pixel779 | pixel780 | pixel781 | pixel782 | pixel783 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 2 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 3 | 4 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 4 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
5 rows × 785 columns
plt.figure(figsize=(8,8))
sample_size = 10
for value in range(10):
digit_ = mnist_digits.query('label == '+str(value)).head(sample_size)
digit_.drop(columns=['label'], inplace=True)
for i, obs_i in enumerate(digit_.iterrows()):
plt.subplot(10, 10, (value*sample_size)+i+1)
sns.heatmap(np.array(obs_i[1]).reshape(28,28), cbar=False)
plt.axis('off')
plt.show()
ax = sns.countplot(data=mnist_digits, x="label")
ax.bar_label(ax.containers[0]);
digit = np.array(mnist_digits.iloc[1][1:])
plt.figure(figsize=(12,12))
sns.heatmap(digit.reshape(28,28), annot=True, fmt="d", cbar=False, square=True)
plt.show()
digit[np.where(digit)[0]] = 1
plt.figure(figsize=(12,12))
sns.heatmap(digit.reshape(28,28), annot=True, fmt="d", cmap="gray", cbar=False, square=True)
plt.show()
binary_digits = mnist_digits.copy()
binary_digits.iloc[:,1:] = (binary_digits.iloc[:,1:]).where(binary_digits.iloc[:,1:] == 0, 1)
binary_digits.head()
| label | pixel0 | pixel1 | pixel2 | pixel3 | pixel4 | pixel5 | pixel6 | pixel7 | pixel8 | ... | pixel774 | pixel775 | pixel776 | pixel777 | pixel778 | pixel779 | pixel780 | pixel781 | pixel782 | pixel783 | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 2 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 3 | 4 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 4 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
5 rows × 785 columns
sns.heatmap(np.array(binary_digits.iloc[1,1:]).reshape(28,28), square=True, cbar=False)
<Axes: >
Sí, es una buena práctica escalar los datos antes de alimentarlos a un perceptrón multicapa u otro tipo de modelo de redes neuronales. El escalado de datos puede ayudar a mejorar el rendimiento y la convergencia del modelo.
Se listan a continuación algunas razones por las cuales es recomendable escalar los datos:
Mayor estabilidad numérica: Al escalar los datos, reduces la varianza de las características, lo que puede ayudar a evitar problemas numéricos durante el entrenamiento del modelo.
Acelera la convergencia: El escalado de datos puede ayudar al algoritmo de optimización a converger más rápidamente hacia la solución óptima, ya que las características están en un rango similar.
Iguala la importancia de las características: Al escalar las características, evitas que las características con valores más grandes dominen las que tienen valores más pequeños, lo que puede garantizar que todas las características contribuyan de manera equitativa al aprendizaje del modelo.
Mejora el rendimiento: En general, los modelos de redes neuronales tienden a funcionar mejor cuando los datos están escalados, lo que puede llevar a una mejor generalización y rendimiento del modelo en datos no vistos.
Algunas técnicas comunes de escalado de datos incluyen la normalización y la estandarización:
Normalización: Escala los datos para que estén en el rango de 0 a 1. Esto se hace restando el valor mínimo y dividiendo por la diferencia entre el valor máximo y el mínimo de cada característica.
Estandarización: Transforma los datos para que tengan una media de 0 y una desviación estándar de 1. Esto se logra restando la media y dividiendo por la desviación estándar de cada característica.
Escalar los datos antes de alimentarlos a un perceptrón multicapa puede mejorar su rendimiento y estabilidad, y es una práctica comúnmente recomendada en el campo del aprendizaje automático.
Tanto la normalización como la estandarización son técnicas comunes para escalar datos antes de alimentarlos a modelos de aprendizaje automático, como perceptrones multicapa y otros tipos de redes neuronales. Sin embargo, la elección entre normalización y estandarización depende del contexto del problema y las características de los datos. A continuación se listan algunas pautas generales sobre cuándo usar cada técnica:
Normalización (min-max scaler*):
Estandarización:
La normalización es más apropiada cuando se desea conservar la relación relativa entre las características y se necesita un rango específico para los datos, mientras que la estandarización es más apropiada cuando se desea eliminar la media y la escala de las características y se asume que los datos siguen una distribución normal. En muchos casos, ambas técnicas pueden dar resultados similares, por lo que puede ser útil probar ambas y evaluar cómo afectan al rendimiento del modelo.
data_scaled = mnist_digits[mnist_digits.columns[:-1]] # leave label out
data_scaled = data_scaled / 255 # Divide over the maximum, equivalente to min-max scaler
ds = pd.DataFrame(data_scaled, columns=mnist_digits.columns[:-1])
ds["label"] = mnist_digits.label
%%time
from sklearn.manifold import TSNE
plt.figure(figsize=(15,15))
p = 30
tsne = TSNE(n_components = 2, perplexity = p, random_state=0)
tsne_results = tsne.fit_transform(ds.drop(columns="label"))
tsne_results=pd.DataFrame(tsne_results, columns=['C1', 'C2'])
tsne_results['digit'] = pd.Categorical(ds.label)
sns.scatterplot(data=tsne_results, x='C1', y='C2',
hue="digit", s=4, alpha=0.5)
plt.title('Perplexity = '+ str(p))
plt.show()
CPU times: user 19min 25s, sys: 5.19 s, total: 19min 30s Wall time: 9min 58s
%%time
from sklearn.neural_network import MLPClassifier
mlp = MLPClassifier(hidden_layer_sizes=(64,), max_iter=500)
X_scaled, y = ds.drop(columns="label"), ds.label
mlp.fit(X_scaled, y)
CPU times: user 2min 29s, sys: 2.89 s, total: 2min 32s Wall time: 1min 16s
MLPClassifier(hidden_layer_sizes=(64,), max_iter=500)In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
MLPClassifier(hidden_layer_sizes=(64,), max_iter=500)
y_pred = mlp.predict(X_scaled)
labels = np.unique(y)
cm = pd.DataFrame(confusion_matrix(y, y_pred, labels=labels),
columns=labels,
index=labels)
sns.heatmap(cm, square=True, annot=True, fmt='d', cbar=False)
plt.xlabel('True label')
plt.ylabel('Predicted label')
print(classification_report(y, y_pred))
precision recall f1-score support
0 1.00 1.00 1.00 4132
1 1.00 1.00 1.00 4684
2 1.00 1.00 1.00 4177
3 0.99 1.00 1.00 4351
4 1.00 1.00 1.00 4072
5 1.00 1.00 1.00 3795
6 1.00 1.00 1.00 4137
7 1.00 1.00 1.00 4401
8 1.00 1.00 1.00 4063
9 1.00 1.00 1.00 4188
accuracy 1.00 42000
macro avg 1.00 1.00 1.00 42000
weighted avg 1.00 1.00 1.00 42000
%%time
X_train, X_test, y_train, y_test = train_test_split(X_scaled, y, test_size=0.3, stratify=y)
mlp.fit(X_train, y_train)
y_pred = mlp.predict(X_test)
labels = np.unique(y)
cm = pd.DataFrame(confusion_matrix(y_test, y_pred, labels=labels),
columns=labels,
index=labels)
sns.heatmap(cm, square=True, annot=True, fmt='d', cbar=False)
plt.xlabel('True label')
plt.ylabel('Predicted label')
print(classification_report(y_test, y_pred))
precision recall f1-score support
0 0.98 0.98 0.98 1240
1 0.98 0.99 0.98 1405
2 0.97 0.97 0.97 1253
3 0.96 0.96 0.96 1305
4 0.96 0.97 0.97 1222
5 0.96 0.96 0.96 1139
6 0.97 0.98 0.98 1241
7 0.97 0.97 0.97 1320
8 0.96 0.94 0.95 1219
9 0.95 0.95 0.95 1256
accuracy 0.97 12600
macro avg 0.97 0.97 0.97 12600
weighted avg 0.97 0.97 0.97 12600
CPU times: user 1min 34s, sys: 1.33 s, total: 1min 35s
Wall time: 48 s
%%time
# Using KFold cross validation
from sklearn.model_selection import KFold
# This is 10-fold cross validation
cv_method = KFold(n_splits=10) # change n_splits for your dataset
# This is the final model you want to deploy
from sklearn.model_selection import cross_val_score
mlp = MLPClassifier(hidden_layer_sizes=(64,), max_iter=1000)
scores = cross_val_score(mlp, X_scaled, y, cv=cv_method)
scores
CPU times: user 26min 48s, sys: 33.2 s, total: 27min 21s Wall time: 13min 53s
array([0.97357143, 0.97285714, 0.97428571, 0.97190476, 0.96738095,
0.96666667, 0.96595238, 0.975 , 0.97095238, 0.97071429])
sns.boxplot(y=scores)
<Axes: >
El conjunto de datos "Auto MPG" contiene información sobre diferentes modelos de automóviles de varios fabricantes y sus respectivas características técnicas. Fue utilizado por primera vez en el artículo "Auto Fuel Efficiency Data" de Ross Quinlan en 1987.
Este conjunto de datos incluye varias características del automóvil, como el cilindraje, la cilindrada, el peso, la potencia y el año del modelo, entre otros. El objetivo principal de este conjunto de datos es predecir la eficiencia del combustible en términos de millas por galón (MPG) de un automóvil en función de sus características.
Descripción de las columnas que se encuentran en este conjunto de datos:
El objetivo típico de análisis o modelado con este conjunto de datos es predecir el rendimiento en términos de millas por galón (MPG) de un automóvil en función de sus características técnicas, lo que puede ser útil para comprender cómo diferentes variables impactan en la eficiencia del combustible y para tomar decisiones informadas sobre la compra o el diseño de automóviles más eficientes en términos de consumo de combustible.
mpg_data = pd.read_csv("https://raw.githubusercontent.com/marsgr6/ml-online/main/data/auto-mpg.csv")
mpg_data.head()
| mpg | cylinders | displacement | horsepower | weight | acceleration | model year | origin | car name | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | 18.0 | 8 | 307.0 | 130.0 | 3504 | 12.0 | 70 | 1 | chevrolet chevelle malibu |
| 1 | 15.0 | 8 | 350.0 | 165.0 | 3693 | 11.5 | 70 | 1 | buick skylark 320 |
| 2 | 18.0 | 8 | 318.0 | 150.0 | 3436 | 11.0 | 70 | 1 | plymouth satellite |
| 3 | 16.0 | 8 | 304.0 | 150.0 | 3433 | 12.0 | 70 | 1 | amc rebel sst |
| 4 | 17.0 | 8 | 302.0 | 140.0 | 3449 | 10.5 | 70 | 1 | ford torino |
mpg_data.isnull().sum()
mpg 0 cylinders 0 displacement 0 horsepower 6 weight 0 acceleration 0 model year 0 origin 0 car name 0 dtype: int64
data_selected = mpg_data.drop(columns="car name") # leave car name out
data_selected['origin'] = data_selected['origin'].map({1: 'USA', 2: 'Europe', 3: 'Japan'})
X, y = data_selected.drop(columns=["mpg", "origin"]), data_selected.mpg
scaler = StandardScaler()
scaler.fit(X)
X_scaled = pd.DataFrame(scaler.transform(X), columns=X.columns)
data_selected.head()
| mpg | cylinders | displacement | horsepower | weight | acceleration | model year | origin | |
|---|---|---|---|---|---|---|---|---|
| 0 | 18.0 | 8 | 307.0 | 130.0 | 3504 | 12.0 | 70 | USA |
| 1 | 15.0 | 8 | 350.0 | 165.0 | 3693 | 11.5 | 70 | USA |
| 2 | 18.0 | 8 | 318.0 | 150.0 | 3436 | 11.0 | 70 | USA |
| 3 | 16.0 | 8 | 304.0 | 150.0 | 3433 | 12.0 | 70 | USA |
| 4 | 17.0 | 8 | 302.0 | 140.0 | 3449 | 10.5 | 70 | USA |
data_selected.describe()
| mpg | cylinders | displacement | horsepower | weight | acceleration | model year | |
|---|---|---|---|---|---|---|---|
| count | 398.000000 | 398.000000 | 398.000000 | 392.000000 | 398.000000 | 398.000000 | 398.000000 |
| mean | 23.514573 | 5.454774 | 193.425879 | 104.469388 | 2970.424623 | 15.568090 | 76.010050 |
| std | 7.815984 | 1.701004 | 104.269838 | 38.491160 | 846.841774 | 2.757689 | 3.697627 |
| min | 9.000000 | 3.000000 | 68.000000 | 46.000000 | 1613.000000 | 8.000000 | 70.000000 |
| 25% | 17.500000 | 4.000000 | 104.250000 | 75.000000 | 2223.750000 | 13.825000 | 73.000000 |
| 50% | 23.000000 | 4.000000 | 148.500000 | 93.500000 | 2803.500000 | 15.500000 | 76.000000 |
| 75% | 29.000000 | 8.000000 | 262.000000 | 126.000000 | 3608.000000 | 17.175000 | 79.000000 |
| max | 46.600000 | 8.000000 | 455.000000 | 230.000000 | 5140.000000 | 24.800000 | 82.000000 |
hue.data_selected.origin = pd.Categorical(data_selected.origin)
sns.pairplot(data=data_selected, hue="origin")
<seaborn.axisgrid.PairGrid at 0x7f8c9dba5510>
En la regresión lineal, la multicolinealidad puede plantear desafíos significativos debido a la forma en que se ajusta el modelo, especialmente al intentar invertir la matriz (X^TX) para estimar los parámetros. La multicolinealidad perfecta hace que la matriz no sea invertible, mientras que la multicolinealidad no perfecta puede llevar a estimaciones de parámetros inexactas o inestables debido al alto número de condición de la matriz.
En contraste, las redes neuronales, incluidas las perceptrones multicapa (MLP), no enfrentan los mismos problemas con la multicolinealidad. Esto se debe a que las redes neuronales generalmente se entrenan utilizando técnicas como la retropropagación, que no requieren invertir matrices o asumir una solución única al problema. Además, las redes neuronales pueden manejar variables de entrada altamente correlacionadas, como se observa en tareas como la clasificación de imágenes, donde las características pueden mostrar fuertes interdependencias.
La flexibilidad inherente de las redes neuronales, combinada con su capacidad para aprender mapeos complejos de entrada a salida, les permite manejar eficazmente la multicolinealidad sin los mismos efectos adversos observados en la regresión lineal. Sin embargo, es importante tener en cuenta que si bien la multicolinealidad puede no ser una preocupación directa en las redes neuronales, otros problemas como el sobreajuste y la desaparición/explotación de gradientes aún pueden afectar el rendimiento y la estabilidad del modelo. Por lo tanto, un diseño cuidadoso del modelo y procedimientos de entrenamiento son cruciales para lograr resultados óptimos en aplicaciones de redes neuronales.
sns.heatmap(data_selected.drop(columns="origin").corr(), annot=True)
<Axes: >
X_scaled.head()
| cylinders | displacement | horsepower | weight | acceleration | model year | |
|---|---|---|---|---|---|---|
| 0 | 1.498191 | 1.090604 | 0.664133 | 0.630870 | -1.295498 | -1.627426 |
| 1 | 1.498191 | 1.503514 | 1.574594 | 0.854333 | -1.477038 | -1.627426 |
| 2 | 1.498191 | 1.196232 | 1.184397 | 0.550470 | -1.658577 | -1.627426 |
| 3 | 1.498191 | 1.061796 | 1.184397 | 0.546923 | -1.295498 | -1.627426 |
| 4 | 1.498191 | 1.042591 | 0.924265 | 0.565841 | -1.840117 | -1.627426 |
X_scaled.isnull().sum()
cylinders 0 displacement 0 horsepower 6 weight 0 acceleration 0 model year 0 dtype: int64
# create an object for KNNImputer
imputer = KNNImputer(n_neighbors=2)
X_scaled = pd.DataFrame(imputer.fit_transform(X_scaled), columns=X.columns)
X_scaled.isnull().sum()
cylinders 0 displacement 0 horsepower 0 weight 0 acceleration 0 model year 0 dtype: int64
%%time
# Using KFold cross validation
from sklearn.model_selection import KFold
# This is the final model you want to deploy
from sklearn.model_selection import cross_val_score
# This is 10-fold cross validation
cv_method = KFold(n_splits=10) # change n_splits for your dataset
mlp = MLPRegressor(hidden_layer_sizes=(512,), max_iter=500)
scores = cross_val_score(mlp, X_scaled, y, cv=cv_method, scoring="r2")
scores
CPU times: user 33.2 s, sys: 448 ms, total: 33.6 s Wall time: 16.9 s
array([0.85934032, 0.871978 , 0.73380954, 0.86680045, 0.73904889,
0.92716591, 0.838334 , 0.84642855, 0.31551456, 0.50828224])
sns.boxplot(y=scores)
<Axes: >
%%time
mlp = MLPRegressor(hidden_layer_sizes=(512,), max_iter=500)
mlp.fit(X_scaled, y)
y_pred = mlp.predict(X_scaled)
mpg_data["mpg_mlp"] = y_pred
CPU times: user 3.54 s, sys: 52 ms, total: 3.6 s Wall time: 1.8 s
Estas son algunas métricas comunes utilizadas para evaluar la calidad de los modelos de regresión:
Error Cuadrático Medio (MSE):
Raíz del Error Cuadrático Medio (RMSE):
Error Absoluto Medio (MAE):
Error Porcentual Absoluto Medio (MAPE):
Coeficiente de Determinación (R²):
Estas métricas son importantes para evaluar la precisión y el rendimiento de los modelos de regresión. Dependiendo del contexto y la naturaleza del problema, se pueden utilizar diferentes métricas para obtener una comprensión completa del rendimiento del modelo.
from sklearn.metrics import mean_squared_error
from sklearn.metrics import mean_absolute_percentage_error
from sklearn.metrics import r2_score
from sklearn.metrics import mean_absolute_error
def plot_evaluation(y, y_pred):
fig = plt.figure(constrained_layout=True, figsize=(12,6))
gs = fig.add_gridspec(3, 3)
ax1 = fig.add_subplot(gs[0, 0])
ax2 = fig.add_subplot(gs[0, 1:])
ax3 = fig.add_subplot(gs[1, 0:])
ax4 = fig.add_subplot(gs[2, 0:])
sns.scatterplot(x=y, y=y_pred, ax=ax1)
ax1.plot(mpg_data.mpg, mpg_data.mpg, 'r') # line x = y
ax2.plot(y, '.:', c="orange", label="Observed")
ax2.plot(y_pred, '.:', c="cornflowerblue", label="Modeled")
ax2.legend()
ax3.plot(y, '.:', c="orange", label="Observed")
ax3.plot(y_pred, '.:', c="cornflowerblue", label="Modeled")
ax3.set_xlim(0, 200)
ax3.legend()
ax4.plot(y, '.:', c="orange", label="Observed")
ax4.plot(y_pred, '.:', c="cornflowerblue", label="Modeled")
ax4.set_xlim(200, 400)
ax4.legend()
print("MSE:", mean_squared_error(y, y_pred))
print("RMSE:", np.sqrt(mean_squared_error(y, y_pred)))
print("MAE:", mean_absolute_error(y, y_pred))
print("MAPE:", mean_absolute_percentage_error(y, y_pred))
print("R2:", r2_score(y, y_pred))
plot_evaluation(y, y_pred)
MSE: 6.6717879117144365 RMSE: 2.5829804319263507 MAE: 1.84982659245948 MAPE: 0.07885705635715959 R2: 0.8905117688907633
Evaluar una regresión de forma visual es una práctica común para comprender cómo se ajusta el modelo a los datos y para identificar posibles problemas como el sobreajuste o subajuste. Algunas técnicas comunes para evaluar una regresión de forma visual son:
Gráfico de dispersión con línea de regresión: Un gráfico de dispersión de los datos reales con una línea que representa la regresión ajustada proporciona una visualización básica del ajuste del modelo a los datos. Puedes ver cómo la línea se ajusta a los puntos de datos y evaluar visualmente si captura la tendencia general de los datos.
Gráfico de residuos: Un gráfico de residuos muestra la diferencia entre los valores reales y las predicciones del modelo (los residuos) en función de las características o la variable de respuesta. Si el modelo es adecuado, los residuos deberían distribuirse aleatoriamente alrededor de cero y no deberían mostrar patrones discernibles.
Diagrama de Q-Q (quantile-quantile): Un diagrama de Q-Q compara los cuantiles de una distribución teórica (como la distribución normal) con los cuantiles de una distribución de residuos. Si los residuos siguen una distribución normal, los puntos en el diagrama de Q-Q deben seguir aproximadamente una línea diagonal.
Gráfico de aprendizaje (learning curve): Un gráfico de aprendizaje muestra cómo el error de entrenamiento y el error de validación cambian a medida que varía el tamaño del conjunto de entrenamiento. Esto puede ayudar a identificar si el modelo está sobreajustando o subajustando los datos y si se beneficiaría de más datos de entrenamiento.
Gráficos de influencia: Estos gráficos muestran cómo cada punto de datos individual influye en los resultados de la regresión. Pueden ayudar a identificar puntos de datos atípicos o influentes que podrían estar afectando el modelo.
Gráficos de predicción vs. observado: Estos gráficos comparan las predicciones del modelo con los valores reales en un conjunto de datos de prueba o validación. Si el modelo es bueno, las predicciones deben seguir una línea diagonal de pendiente 1.
Estas son solo algunas técnicas que puedes utilizar para evaluar una regresión de forma visual. La elección de las técnicas dependerá del modelo específico y del tipo de datos que estés analizando. Utilizar una combinación de estas visualizaciones puede proporcionar una comprensión más completa del rendimiento del modelo de regresión.
Una Deep Neural Network (DNN) feedforward es un tipo específico de red neuronal artificial (ANN) donde la información fluye en una sola dirección, desde la capa de entrada hacia la capa de salida, sin conexiones hacia atrás o ciclos en la estructura de la red. En este tipo de arquitectura, cada capa de neuronas está completamente conectada a la capa siguiente, lo que significa que cada neurona en una capa envía señales a todas las neuronas de la capa siguiente.
Una DNN feedforward típicamente consta de múltiples capas ocultas entre la capa de entrada y la capa de salida, lo que permite al modelo aprender representaciones jerárquicas y complejas de los datos de entrada. Cada neurona en una capa oculta realiza una combinación lineal de las salidas de todas las neuronas en la capa anterior, seguida de la aplicación de una función de activación no lineal. Estas funciones de activación introducen no linealidades en el modelo, permitiendo la captura de patrones y relaciones no lineales en los datos.
Una diferencia importante entre una DNN feedforward y un Multi-Layer Perceptron (MLP) es que el término "MLP" se utiliza a menudo de manera intercambiable con "DNN feedforward". Ambos términos se refieren a redes neuronales con múltiples capas ocultas donde la información fluye en una sola dirección. Sin embargo, es importante destacar que "MLP" a menudo se usa para referirse a arquitecturas de redes neuronales más simples, especialmente cuando se usan solo una o dos capas ocultas. Por otro lado, "DNN" se refiere generalmente a arquitecturas de redes neuronales más profundas con varias capas ocultas.
En resumen, una DNN feedforward es un tipo de red neuronal donde la información fluye en una sola dirección, desde la capa de entrada hacia la capa de salida, y puede tener múltiples capas ocultas para aprender representaciones complejas de los datos. El término "MLP" a menudo se usa de manera intercambiable con "DNN feedforward", pero a veces se refiere a arquitecturas de redes neuronales más simples con menos capas ocultas.
import tensorflow as tf
from tensorflow import keras
from tensorflow.keras import layers
#from tensorflow.keras.layers.experimental import preprocessing
2024-02-15 18:59:31.252266: I tensorflow/core/platform/cpu_feature_guard.cc:182] This TensorFlow binary is optimized to use available CPU instructions in performance-critical operations. To enable the following instructions: SSE4.1 SSE4.2 AVX AVX2 FMA, in other operations, rebuild TensorFlow with the appropriate compiler flags.
data = pd.DataFrame(scaler.inverse_transform(X_scaled), columns=X_scaled.columns)
data["mpg"] = y
data.head()
| cylinders | displacement | horsepower | weight | acceleration | model year | mpg | |
|---|---|---|---|---|---|---|---|
| 0 | 8.0 | 307.0 | 130.0 | 3504.0 | 12.0 | 70.0 | 18.0 |
| 1 | 8.0 | 350.0 | 165.0 | 3693.0 | 11.5 | 70.0 | 15.0 |
| 2 | 8.0 | 318.0 | 150.0 | 3436.0 | 11.0 | 70.0 | 18.0 |
| 3 | 8.0 | 304.0 | 150.0 | 3433.0 | 12.0 | 70.0 | 16.0 |
| 4 | 8.0 | 302.0 | 140.0 | 3449.0 | 10.5 | 70.0 | 17.0 |
train_data = data.sample(frac=0.8, random_state=0)
test_data = data.drop(train_data.index)
train_data.shape, test_data.shape
((318, 7), (80, 7))
train_features = train_data.copy()
test_features = test_data.copy()
train_target = train_features.pop('mpg')
test_target = test_features.pop('mpg')
normalizer = tf.keras.layers.Normalization(axis=-1)
normalizer.adapt(np.array(train_features))
2024-02-15 18:59:33.739417: I tensorflow/core/common_runtime/process_util.cc:146] Creating new thread pool with default inter op setting: 2. Tune using inter_op_parallelism_threads for best performance.
def build_and_compile_model(norm):
model = keras.Sequential([
norm,
layers.Dense(256, activation='relu'),
layers.Dense(256, activation='relu'),
layers.Dense(1)
])
model.compile(loss='mean_absolute_error',
optimizer=tf.keras.optimizers.Adam(0.001))
return model
dnn_model = build_and_compile_model(normalizer)
dnn_model.summary()
Model: "sequential"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
normalization (Normalizatio (None, 6) 13
n)
dense (Dense) (None, 256) 1792
dense_1 (Dense) (None, 256) 65792
dense_2 (Dense) (None, 1) 257
=================================================================
Total params: 67,854
Trainable params: 67,841
Non-trainable params: 13
_________________________________________________________________
%%time
history = dnn_model.fit(
train_features,
train_target,
validation_split=0.2,
verbose=0, epochs=100)
CPU times: user 26.7 s, sys: 28.7 s, total: 55.5 s Wall time: 21.4 s
def plot_loss(history):
plt.plot(history.history['loss'], label='loss')
plt.plot(history.history['val_loss'], label='val_loss')
plt.ylim([0, 10])
plt.xlabel('Epoch')
plt.ylabel('Loss [MPG]')
plt.legend()
plt.grid(True)
plot_loss(history)
Un gráfico de pérdida de entrenamiento (train loss) versus pérdida de prueba (test loss) es una representación visual que muestra cómo cambian las pérdidas (errores) del modelo durante el entrenamiento y la evaluación en un conjunto de datos de prueba o validación.
Pérdida de entrenamiento (train loss): Es la métrica de error que se calcula utilizando los datos de entrenamiento durante el proceso de entrenamiento del modelo. Mide qué tan bien se ajusta el modelo a los datos de entrenamiento.
Pérdida de prueba (test loss): Es la métrica de error que se calcula utilizando un conjunto de datos independiente, no visto por el modelo durante el entrenamiento. Se utiliza para evaluar el rendimiento del modelo en datos no vistos y para estimar su capacidad de generalización.
En el gráfico, el eje x representa las iteraciones o épocas de entrenamiento, mientras que el eje y representa la pérdida (error). Las curvas de pérdida de entrenamiento y prueba se trazan en el mismo gráfico para comparar cómo se comporta el modelo durante el entrenamiento y la evaluación.
Idealmente, durante el entrenamiento del modelo, tanto la pérdida de entrenamiento como la pérdida de prueba disminuirán con el tiempo. Sin embargo, si el modelo comienza a sobreajustar los datos de entrenamiento, es probable que la pérdida de prueba aumente, mientras que la pérdida de entrenamiento continuará disminuyendo. Esto indica que el modelo está aprendiendo a ajustarse demasiado bien a los datos de entrenamiento y no puede generalizar bien a nuevos datos.
Un gráfico de pérdida de entrenamiento versus pérdida de prueba es una herramienta útil para monitorear el rendimiento del modelo durante el entrenamiento y la evaluación, y para identificar problemas de sobreajuste o subajuste. Si las curvas de pérdida de entrenamiento y prueba divergen significativamente, es posible que sea necesario ajustar la complejidad del modelo, aplicar técnicas de regularización o recopilar más datos para mejorar la generalización del modelo.
Una función de pérdida, o loss function en inglés, es una medida que cuantifica la discrepancia entre las predicciones de un modelo de aprendizaje automático y los valores reales del conjunto de datos de entrenamiento. Su importancia radica en varios aspectos clave del proceso de entrenamiento y evaluación de modelos en machine learning:
Optimización del modelo: Durante el entrenamiento, el objetivo principal es minimizar la función de pérdida. Esto guía al modelo a ajustar sus parámetros de manera que las predicciones se acerquen lo más posible a los valores reales.
Evaluación del rendimiento: La función de pérdida proporciona una medida cuantitativa del rendimiento del modelo. Cuanto menor sea la pérdida, mejor será el rendimiento del modelo en el conjunto de datos de entrenamiento.
Comparación entre modelos: Permite comparar el rendimiento de diferentes modelos en un problema dado. Los modelos que minimizan la función de pérdida de manera más efectiva suelen producir predicciones más precisas.
Regularización: Algunas funciones de pérdida incluyen términos de regularización que penalizan modelos más complejos, ayudando a prevenir el sobreajuste.
Ajuste del modelo a objetivos específicos: Dependiendo del tipo de problema y los objetivos específicos, se pueden seleccionar diferentes funciones de pérdida para optimizar el modelo de manera acorde a dichos objetivos (por ejemplo, minimizar errores cuadráticos para regresión o entropía cruzada para clasificación).
La función de pérdida es un componente fundamental en el entrenamiento y evaluación de modelos de aprendizaje automático, ya que guía el proceso de optimización del modelo y proporciona una medida cuantitativa del rendimiento del mismo.
Aquí tienes las definiciones resumidas de las funciones de pérdida comunes:
Error Cuadrático Medio (MSE - Mean Squared Error):
Error Absoluto Medio (MAE - Mean Absolute Error):
Error Cuadrático Medio Logarítmico (MSLE):
Pérdida de Huber:
La pérdida de Huber es una función de pérdida que combina las ventajas del error cuadrático medio (MSE) y el error absoluto medio (MAE). Es especialmente útil en problemas de regresión donde pueden existir datos atípicos o ruido en los datos.
La fórmula de la pérdida de Huber para una sola muestra se puede expresar como:
$$ L(y, \hat{y}) = \begin{cases} \frac{1}{2}(y - \hat{y})^2, & \text{si } |y - \hat{y}| \leq \delta \\ \delta(|y - \hat{y}| - \frac{1}{2}\delta), & \text{en otro caso} \end{cases} $$Donde:
Cuando la diferencia absoluta entre el valor verdadero y la predicción es menor o igual que $ \delta $, la pérdida de Huber se comporta como el error cuadrático medio, lo que significa que penaliza los errores cuadráticos de manera similar al MSE. Sin embargo, cuando la diferencia absoluta supera $ \delta $, la pérdida de Huber cambia a un comportamiento lineal, similar al error absoluto medio. Esto permite que la pérdida de Huber sea menos sensible a los valores atípicos en los datos en comparación con el MSE.
En resumen, la pérdida de Huber es una alternativa robusta al MSE y al MAE, que puede ser útil en situaciones donde se desea una penalización más suave de los errores grandes.
Entropía Cruzada Binaria (Binary Crossentropy):
Pérdida de Hinge:
La pérdida de Hinge, también conocida como pérdida de bisagra, es una función de pérdida comúnmente utilizada en problemas de clasificación binaria y en máquinas de vectores de soporte (SVM). Esta función de pérdida es especialmente útil cuando se trabaja con clasificadores que generan una salida continua en lugar de probabilidades.
La fórmula de la pérdida de Hinge para una sola muestra se puede expresar como:
$$ L(y, \hat{y}) = \max(0, 1 - y \cdot \hat{y}) $$Donde:
La pérdida de Hinge penaliza las predicciones incorrectas con un valor proporcional a la distancia entre la predicción y el margen de decisión (1 en este caso). Si la predicción es correcta (es decir, $ y \cdot \hat{y} $ es mayor que 1), la pérdida es cero. Si la predicción está en el lado incorrecto del margen, la pérdida aumenta linealmente con la distancia al margen.
En resumen, el objetivo del entrenamiento es minimizar esta función de pérdida para mejorar la capacidad del modelo para clasificar correctamente las muestras.
Entropía Cruzada Categórica (Categorical Crossentropy):
Pérdida Exponencial:
La pérdida exponencial es una función de pérdida utilizada en problemas de clasificación multiclase. A menudo se emplea en conjunción con modelos de clasificación como redes neuronales. Esta función de pérdida es similar a la pérdida softmax, pero penaliza de manera más intensa las clasificaciones incorrectas.
La fórmula de la pérdida exponencial para una sola muestra se puede expresar como:
$$ L(y, \hat{y}) = - \sum_{i} y_i \cdot \exp(\hat{y}_i) $$Donde:
Al igual que la pérdida softmax, la función de pérdida exponencial penaliza más fuertemente las clasificaciones incorrectas, pero utilizando la exponencial de la probabilidad predicha en lugar del logaritmo. El objetivo del entrenamiento es minimizar esta función de pérdida para mejorar la precisión de las predicciones del modelo.
La pérdida Softmax, también conocida como entropía cruzada categórica, se utiliza comúnmente en problemas de clasificación multiclase. Esta función de pérdida mide la discrepancia entre las distribuciones de probabilidad de las clases predichas y las clases reales.
La fórmula de la pérdida Softmax para una sola muestra se puede expresar como:
$$ L(y, \hat{y}) = - \sum_{i} y_i \cdot \log(\hat{y}_i) $$Donde:
La función de pérdida Softmax penaliza más fuertemente las clasificaciones incorrectas, ya que la diferencia entre las probabilidades reales y las predichas se multiplica por el logaritmo de la probabilidad predicha. El objetivo del entrenamiento es minimizar esta función de pérdida para mejorar la precisión de las predicciones del modelo.
En el contexto de modelos generativos, aquí están las definiciones de las funciones de pérdida mencionadas:
Divergencia de Kullback-Leibler (KL Divergence):
Pérdida del Autoencoder:
Estas funciones de pérdida se utilizan en el proceso de entrenamiento de modelos generativos para optimizar los parámetros del modelo y mejorar su capacidad para generar muestras realistas o reconstruir adecuadamente las entradas originales.
test_predictions = dnn_model.predict(test_features).flatten()
test_predictions = pd.Series(test_predictions, index=test_features.index)
plot_evaluation(test_target, test_predictions)
3/3 [==============================] - 0s 15ms/step MSE: 6.9630055221161005 RMSE: 2.6387507502824326 MAE: 1.9365879344940187 MAPE: 0.08398161471412509 R2: 0.8731464086428874
La evaluación de validación cruzada (CV, del inglés Cross-Validation) es una técnica utilizada en aprendizaje automático para evaluar el rendimiento de un modelo estadístico. El propósito principal de la validación cruzada es estimar la capacidad de generalización del modelo a datos no vistos.
En la validación cruzada, el conjunto de datos se divide en k subconjuntos más pequeños, llamados pliegues (folds). El modelo se entrena k veces, cada vez utilizando k-1 pliegues como datos de entrenamiento y uno de los pliegues restantes como datos de prueba. Luego, se calcula la métrica de evaluación (como la precisión, el error, el área bajo la curva ROC, etc.) en cada iteración y se promedian para obtener una estimación final del rendimiento del modelo.
Los tipos comunes de validación cruzada incluyen:
Validación cruzada de k-fold (k-fold cross-validation): El conjunto de datos se divide en k pliegues de igual tamaño. El modelo se entrena k veces, cada vez utilizando k-1 pliegues como datos de entrenamiento y uno de los pliegues restantes como datos de prueba.
Validación cruzada de leave-one-out (LOOCV): Es una variante de la validación cruzada de k-fold donde k es igual al número total de muestras en el conjunto de datos. En cada iteración, una única muestra se utiliza como conjunto de prueba y todas las demás se utilizan como conjunto de entrenamiento. Útil para datasets pequeños (very small data).
Validación cruzada estratificada: Es una variante de la validación cruzada k-fold donde se asegura que cada clase esté representada proporcionalmente en cada pliegue. Esto es particularmente útil en problemas de clasificación desequilibrados.
La validación cruzada es una técnica robusta y ampliamente utilizada para evaluar modelos de aprendizaje automático, ya que proporciona una estimación más confiable del rendimiento del modelo en datos no vistos que simplemente dividir los datos en un conjunto de entrenamiento y un conjunto de prueba. Además, ayuda a reducir el sesgo en la estimación del rendimiento del modelo al utilizar múltiples particiones de los datos.
%%time
# CV evaluation
kfold = KFold(n_splits=10)
scores_reg = []
Xdnn, ydnn = data.drop(columns="mpg"), data.mpg
for train_index, test_index in kfold.split(Xdnn, ydnn):
# Split data
X_train, X_test = Xdnn.iloc[train_index], Xdnn.iloc[test_index]
y_train, y_test = ydnn[train_index], ydnn[test_index]
normalizer = tf.keras.layers.Normalization(axis=-1)
normalizer.adapt(np.array(X_train))
dnn_model = build_and_compile_model(normalizer)
history = dnn_model.fit(
X_train,
y_train,
validation_split=0.0,
verbose=0, epochs=100)
# Predict
y_pred = dnn_model.predict(X_test).flatten()
scores_reg += [r2_score(y_test, y_pred)]
2/2 [==============================] - 0s 23ms/step 2/2 [==============================] - 0s 20ms/step 2/2 [==============================] - 0s 34ms/step WARNING:tensorflow:5 out of the last 10 calls to <function Model.make_predict_function.<locals>.predict_function at 0x7f8c71235800> triggered tf.function retracing. Tracing is expensive and the excessive number of tracings could be due to (1) creating @tf.function repeatedly in a loop, (2) passing tensors with different shapes, (3) passing Python objects instead of tensors. For (1), please define your @tf.function outside of the loop. For (2), @tf.function has reduce_retracing=True option that can avoid unnecessary retracing. For (3), please refer to https://www.tensorflow.org/guide/function#controlling_retracing and https://www.tensorflow.org/api_docs/python/tf/function for more details. 2/2 [==============================] - 0s 31ms/step WARNING:tensorflow:6 out of the last 12 calls to <function Model.make_predict_function.<locals>.predict_function at 0x7f8c7014c400> triggered tf.function retracing. Tracing is expensive and the excessive number of tracings could be due to (1) creating @tf.function repeatedly in a loop, (2) passing tensors with different shapes, (3) passing Python objects instead of tensors. For (1), please define your @tf.function outside of the loop. For (2), @tf.function has reduce_retracing=True option that can avoid unnecessary retracing. For (3), please refer to https://www.tensorflow.org/guide/function#controlling_retracing and https://www.tensorflow.org/api_docs/python/tf/function for more details. 2/2 [==============================] - 0s 27ms/step 2/2 [==============================] - 0s 14ms/step 2/2 [==============================] - 0s 13ms/step 2/2 [==============================] - 0s 34ms/step 2/2 [==============================] - 0s 14ms/step 2/2 [==============================] - 0s 13ms/step CPU times: user 2min 33s, sys: 2min 26s, total: 4min 59s Wall time: 1min 43s
data_eval = pd.melt(pd.DataFrame({"MLP": scores, "DNN": scores_reg}))
data_eval.columns = ["Model", "R2"]
sns.boxplot(data=data_eval, x="Model", y="R2", hue="Model")
sns.lineplot(data=data_eval, x='Model', y="R2", err_style="bars", c="orange")
sns.swarmplot(data=data_eval, x='Model', y="R2", c="orange")
plt.plot(data_eval.groupby('Model').mean(), 'Pr')
[<matplotlib.lines.Line2D at 0x7f8c71fcb4d0>]
import tensorflow.keras as keras
(trainX, trainY), (testX, testY) = keras.datasets.mnist.load_data()
Downloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz 11490434/11490434 [==============================] - 8s 1us/step
from tensorflow.keras import Sequential
from tensorflow.keras.layers import Dense, Input, Flatten
model = Sequential([
Input(shape=(28,28,1,)),
Flatten(),
Dense(units=84, activation="relu"),
Dense(units=10, activation="softmax"),
])
print (model.summary())
Model: "sequential_11"
_________________________________________________________________
Layer (type) Output Shape Param #
=================================================================
flatten (Flatten) (None, 784) 0
dense_33 (Dense) (None, 84) 65940
dense_34 (Dense) (None, 10) 850
=================================================================
Total params: 66,790
Trainable params: 66,790
Non-trainable params: 0
_________________________________________________________________
None
model.compile(optimizer="adam", loss=tf.keras.losses.SparseCategoricalCrossentropy(), metrics=["acc"])
history = model.fit(x=trainX, y=trainY, batch_size=256, epochs=10, validation_data=(testX, testY))
Epoch 1/10 235/235 [==============================] - 3s 11ms/step - loss: 9.6618 - acc: 0.7986 - val_loss: 2.2449 - val_acc: 0.8745 Epoch 2/10 235/235 [==============================] - 2s 10ms/step - loss: 1.4748 - acc: 0.8912 - val_loss: 1.1196 - val_acc: 0.8841 Epoch 3/10 235/235 [==============================] - 2s 9ms/step - loss: 0.7915 - acc: 0.8923 - val_loss: 0.7862 - val_acc: 0.8836 Epoch 4/10 235/235 [==============================] - 2s 10ms/step - loss: 0.5146 - acc: 0.9064 - val_loss: 0.6106 - val_acc: 0.9030 Epoch 5/10 235/235 [==============================] - 2s 9ms/step - loss: 0.3894 - acc: 0.9190 - val_loss: 0.5542 - val_acc: 0.9106 Epoch 6/10 235/235 [==============================] - 2s 11ms/step - loss: 0.3073 - acc: 0.9302 - val_loss: 0.5115 - val_acc: 0.9179 Epoch 7/10 235/235 [==============================] - 3s 13ms/step - loss: 0.2540 - acc: 0.9389 - val_loss: 0.4842 - val_acc: 0.9274 Epoch 8/10 235/235 [==============================] - 3s 12ms/step - loss: 0.2121 - acc: 0.9449 - val_loss: 0.4781 - val_acc: 0.9255 Epoch 9/10 235/235 [==============================] - 3s 12ms/step - loss: 0.1923 - acc: 0.9491 - val_loss: 0.4434 - val_acc: 0.9287 Epoch 10/10 235/235 [==============================] - 3s 14ms/step - loss: 0.1692 - acc: 0.9543 - val_loss: 0.4201 - val_acc: 0.9373
plot_loss(history)
Research: